﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers.CSharp;
using CompleterLib.Util;

namespace CompleterLib.AbstractTypes
{
    public abstract class TypeTerm : System.IEquatable<TypeTerm>
    {
        public TypeTerm Replace(TypeTerm var, TypeTerm replacement)
        {
            if (this.Equals(var))
            {
                return replacement;
            }
            else
            {
                return this;
            }
        }
        public override abstract bool Equals(object obj);
        public override abstract int GetHashCode();
        public override abstract string ToString();

        public bool Equals(TypeTerm obj)
        {
            return this.Equals((object)obj);
        }
    }
    static class TypeTermUtil
    {
        public static IdentifiedTypeTerm AsTypeTerm(this FieldSymbol field)
        {
            return new NamedIdentifiedTypeTerm(field);
        }
        public static IdentifiedTypeTerm AsTypeTerm(this PropertySymbol prop)
        {
            return new NamedIdentifiedTypeTerm(prop);
        }
    }

    abstract class IdentifiedTypeTerm : TypeTerm
    { }
    class AnonymousTypeTerm : TypeTerm
    {
        private static int nextId = 0;
        private readonly int id;
        public AnonymousTypeTerm()
        {
            this.id = nextId++;
        }
        public override string ToString()
        {
            return "AnonymousTypeTerm(" + id + ")";
        }
        public override int GetHashCode()
        {
            return id.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is AnonymousTypeTerm && id == (obj as AnonymousTypeTerm).id;
        }
    }
    class NamedTypeTerm : TypeTerm
    {
        private readonly string name;
        public NamedTypeTerm(string name)
        {
            this.name = name;
        }
        public NamedTypeTerm(MethodSymbol method, string name)
            : this(method.GetQualifiedName()
                + ":::" + name)
        { }
        public NamedTypeTerm(LocalSymbol local)
            : this(local.ContainingMethod, local.Name.ToString())
        { }
        public override string ToString()
        {
            return "NamedTypeTerm(" + name + ")";
        }
        public override int GetHashCode()
        {
            return name.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is NamedTypeTerm && name.Equals((obj as NamedTypeTerm).name);
        }
    }
    class NamedIdentifiedTypeTerm : IdentifiedTypeTerm
    {
        private readonly string name;
        public NamedIdentifiedTypeTerm(string name)
        {
            this.name = name;
        }
        public NamedIdentifiedTypeTerm(NamedTypeSymbol type, string name)
            // TODO Does type.ToString() properly include `arity?
            : this(type.ToString() + ":::" + name)
        { }
        public NamedIdentifiedTypeTerm(FieldSymbol mem)
            : this(mem.ContainingType, mem.Name.ToString())
        { }

        public NamedIdentifiedTypeTerm(PropertySymbol mem)
            : this(mem.ContainingType, mem.Name.ToString())
        { }
        public override string ToString()
        {
            return "NamedIdentifiedTypeTerm(" + name + ")";
        }
        public override int GetHashCode()
        {
            return name.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is NamedIdentifiedTypeTerm
                && name.Equals((obj as NamedIdentifiedTypeTerm).name);
        }
    }
    abstract class FunctionSignatureTypeTerm : IdentifiedTypeTerm
    {
        private readonly MethodSymbol func;

        public FunctionSignatureTypeTerm(MethodSymbol func)
        {
            if (func.IsOverride)
            {
                this.func = func.GetBaseOfOverride();
            }
            else
            {
                this.func = func;
            }
        }

        public MethodSymbol Method
        {
            get { return func; }
        }

        public virtual bool IsPropertySet
        {
            get { return false; }
        }
        public virtual bool IsPropertyGet
        {
            get { return false; }
        }
    }

    class FormalParameterTypeTerm : FunctionSignatureTypeTerm
    {
        private readonly int position;

        public FormalParameterTypeTerm(MethodSymbol func, int position)
            : base(func)
        {
            this.position = position;
        }

        public override bool IsPropertySet
        {
            get { return position == 1 && Method.Name.ToString().StartsWith("set_"); }
        }

        public override string ToString()
        {
            return "FormalParameterTypeTerm(func="
                + Method.GetQualifiedName()
                + ", position=" + position + ")";
        }
        public override int GetHashCode()
        {
            return Method.GetHashCode() ^ position.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is FormalParameterTypeTerm
                && position == (obj as FormalParameterTypeTerm).position
                && Method.HasSameMethodName((obj as FormalParameterTypeTerm).Method);
        }
    }
    class ReturnTypeTypeTerm : FunctionSignatureTypeTerm
    {
        public ReturnTypeTypeTerm(MethodSymbol func)
            : base(func)
        { }

        public override bool IsPropertyGet
        {
            get { return Method.Name.ToString().StartsWith("get_"); }
        }

        public override string ToString()
        {
            return "ReturnTypeTypeTerm(func=" + Method.GetQualifiedName()
                + ";type=" + Method.ReturnType + ")";
        }
        public override int GetHashCode()
        {
            return Method.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is ReturnTypeTypeTerm
                && Method.HasSameMethodName((obj as ReturnTypeTypeTerm).Method);
        }
    }

    class ConstantTypeTerm : TypeTerm
    {
        private readonly string str;
        public ConstantTypeTerm(object obj)
        {
            this.str = obj == null ? "null" : obj.ToString();
        }
        public override string ToString()
        {
            return "ConstantTypeTerm(" + str + ")";
        }
        protected string String { get { return str; } }
        public override int GetHashCode()
        {
            return str.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            // Identical constant strings are still not equal
            return this == obj;
        }
    }
    class ConstantStringTypeTerm : ConstantTypeTerm
    {
        public ConstantStringTypeTerm(string str)
            : base(str)
        { }
        public override string ToString()
        {
            return "ConstantStringTypeTerm(\"" + String + "\")";
        }
    }
}
